#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/platform_device.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>
#include <linux/io.h>
#include <linux/errno.h>
#include "linux/securec.h"
#include "PCA6416.h"

dev_t devno;
static struct class *cls = NULL;
static struct device *node_device = NULL;
struct cdev node_cdev;
struct mutex i2c_dev_array_lock;
static struct PCA6416_dev *i2c_dev_array[PCA6416_I2C_MINORS] = {NULL};

static const struct i2c_device_id PCA6416_idtable[] = {
    { "PCA6416", 0 },
    { }
};

/*lint -e508 */
MODULE_DEVICE_TABLE(i2c, PCA6416_idtable);
/*lint +e508 */

STATIC struct PCA6416_dev *get_free_i2c_dev(struct i2c_adapter *adap)
{
    struct PCA6416_dev *i2c_dev = NULL;

    if (adap == NULL) {
        pca6416_err("invalid parameter,adap == NULL\n");
        return NULL;
    }

    if (adap->nr >= PCA6416_I2C_MINORS) {
        pca6416_err("invalid parameter,adap->nr = %d\n", adap->nr);
        return NULL;
    }

    i2c_dev = kzalloc(sizeof(struct PCA6416_dev), GFP_KERNEL);
    if (i2c_dev == NULL) {
        pca6416_err("kzalloc i2c_dev error \n");
        return NULL;
    }

    mutex_lock(&i2c_dev_array_lock);
    if (i2c_dev_array[adap->nr] != NULL) {
        mutex_unlock(&i2c_dev_array_lock);
        pca6416_err("PCA64xx already has a device assigned to this adapter\n");
        goto exit;
    }

    pca6416_info("adap->nr:%d\n", adap->nr);
    i2c_dev->minor = adap->nr;
    mutex_init(&(i2c_dev->mutex));
    i2c_dev_array[adap->nr] = i2c_dev;
    mutex_unlock(&i2c_dev_array_lock);

    return i2c_dev;
exit:
    kfree(i2c_dev);
    i2c_dev = NULL;
    return NULL;
}

STATIC int return_i2c_dev(struct PCA6416_dev *i2c_dev)
{
    if (i2c_dev == NULL) {
        pca6416_err("invalid parameter,i2c_dev == NULL\n");
        return -1;
    }

    if (i2c_dev->minor >= PCA6416_I2C_MINORS) {
        pca6416_err("invalid parameter,i2c_dev->minor = %d\n", i2c_dev->minor);
        return -1;
    }
    mutex_lock(&i2c_dev_array_lock);
    i2c_dev_array[i2c_dev->minor] = NULL;
    mutex_unlock(&i2c_dev_array_lock);
    (void)mutex_destroy(&(i2c_dev->mutex));
    kfree(i2c_dev);
    i2c_dev = NULL;
    return 0;
}

STATIC struct PCA6416_dev *i2c_dev_get_by_adapter(struct i2c_adapter *adap)
{
    struct PCA6416_dev *i2c_dev = NULL;

    if (adap == NULL) {
        pca6416_err("invalid parameter,adap == NULL\n");
        return NULL;
    }
    if (adap->nr >= PCA6416_I2C_MINORS) {
        pca6416_err("invalid parameter,adap->nr = %d\n", adap->nr);
        return NULL;
    }

    mutex_lock(&i2c_dev_array_lock);
    if ((i2c_dev_array[adap->nr] != NULL) && (i2c_dev_array[adap->nr]->adap == adap))
        i2c_dev = i2c_dev_array[adap->nr];
    else
        i2c_dev = NULL;
    mutex_unlock(&i2c_dev_array_lock);

    return i2c_dev;
}

STATIC struct PCA6416_dev *i2c_dev_get_by_minor(unsigned index)
{
    struct PCA6416_dev *i2c_dev = NULL;

    if (index >= PCA6416_I2C_MINORS) {
        pca6416_err("invalid parameter,index = %d\n", index);
        return NULL;
    }

    mutex_lock(&i2c_dev_array_lock);
    i2c_dev = i2c_dev_array[index];
    mutex_unlock(&i2c_dev_array_lock);

    return i2c_dev;
}

STATIC int PCA6416_open(struct inode *node, struct file *file)
{
    unsigned int minor;
    struct PCA6416_dev *i2c_dev = NULL;

    pca6416_info("PCA64xx_dev open\n");

    minor = iminor(node);
    pca6416_info("minor:%d\n", minor);
    i2c_dev = i2c_dev_get_by_minor(minor);
    if (i2c_dev == NULL) {
        pca6416_err("i2c_dev is NULL, PCA64xx_open fail!\n");
        return -ENODEV;
    }

    file->private_data = i2c_dev;

    return 0;
}

STATIC int PCA6416_release(struct inode *node, struct file *file)
{
    struct PCA6416_dev *i2c_dev = (struct PCA6416_dev *)file->private_data;

    if (i2c_dev == NULL) {
        pca6416_err("invalid parameter,i2c_dev == NULL\n");
        return -1;
    }

    file->private_data = NULL;
    pca6416_info("PCA64xx release\n");

    return 0;
}

STATIC int PCA6416_i2c_read(struct i2c_client *client, char reg, char *data)
{
    int ret;

    ret = i2c_master_send(client, &reg, PCA6416_SEND_BUF_LEN);
    if (ret != 1) {
        pca6416_err("PCA64xx ioctrl master_send error! ret = %d\n", ret);
        return -EFAULT;
    }

    ret = i2c_master_recv(client, data, PCA6416_READ_BUF_LEN);
    if (ret != 1) {
        pca6416_err("PCA64xx ioctrl master_recv recv fail! ret = %d\n", ret);
        return -EFAULT;
    }
    mdelay(PCA6416_DELAY_MS);


    return 0;
}

STATIC char PCA6416_get_id(int board_id, char id_read, unsigned int cmd)
{
    char ret_id = 0;
    if (cmd == IIC_PCA_IOCTL_GET_PCB_ID) {
        if (board_id == BOARD_ID_DEVELOP_C) {
            ret_id = (char)(((unsigned char)id_read >> PCA9555_PCB_ID_BITS) & BOARD_ID_DEVELOP_C_MASK);
        } else {
            ret_id = (char)(((unsigned char)id_read >> PCA6416_PCB_ID_BITS) & BOARD_ID_DEVELOP_A_MASK);
        }
    } else if (cmd == IIC_PCA_IOCTL_GET_BOM_ID) {
        if (board_id == BOARD_ID_DEVELOP_C) {
            ret_id = (char)(((unsigned char)id_read) & BOARD_ID_DEVELOP_C_MASK);
        } else {
            ret_id = (char)(((unsigned char)id_read) & BOARD_ID_DEVELOP_A_MASK);
        }
    }
    return ret_id;
}

STATIC int PCA6416_get_board_id(struct i2c_client *client)
{
    char read_buf[PCA6416_READ_BUF_LEN] = {0};
    int board_id = BOARD_ID_DEVELOP_A;
    int ret;

    ret = PCA6416_i2c_read(client, BOARD_INFO_CMD, read_buf);
    if (ret == 0) {
        board_id = (int)read_buf[0];
    }

    return board_id;
}

STATIC long PCA6416_ioctl(struct file *file, unsigned int cmd, unsigned long ul_arg)
{
    struct PCA6416_dev *i2c_dev = (struct PCA6416_dev *)file->private_data;
    struct i2c_client *client = NULL;
    char read_buf[PCA6416_READ_BUF_LEN] = {0};
    int ret = 0;
    char *arg = NULL;
    int board_id;

    arg = (char *)(uintptr_t)ul_arg;

    if (i2c_dev == NULL || arg == NULL) {
        pca6416_err("invalid parameter,i2c_dev or arg is NULL\n");
        return -1;
    }

    client = i2c_dev->client;

    switch (cmd) {
        case IIC_PCA_IOCTL_GET_BOARD_ID:
            mutex_lock(&(i2c_dev->mutex));
            ret = PCA6416_i2c_read(client, BOARD_INFO_CMD, read_buf);
            if (ret != 0) {
                mutex_unlock(&(i2c_dev->mutex));
                pca6416_err("PCA64xx_i2c_read fail\n");
                return ret;
            }
            mutex_unlock(&(i2c_dev->mutex));
            break;

        case IIC_PCA_IOCTL_GET_PCB_ID:
            mutex_lock(&(i2c_dev->mutex));
            ret = PCA6416_i2c_read(client, PCB_INFO_CMD, read_buf);
            if (ret != 0) {
                mutex_unlock(&(i2c_dev->mutex));
                pca6416_err("PCA64xx_i2c_read fail\n");
                return ret;
            }
            board_id = PCA6416_get_board_id(client);
            mutex_unlock(&(i2c_dev->mutex));
            read_buf[0] = PCA6416_get_id(board_id, read_buf[0], IIC_PCA_IOCTL_GET_PCB_ID);
            break;

        case IIC_PCA_IOCTL_GET_BOM_ID:
            mutex_lock(&(i2c_dev->mutex));
            ret = PCA6416_i2c_read(client, BOM_INFO_CMD, read_buf);
            if (ret != 0) {
                mutex_unlock(&(i2c_dev->mutex));
                pca6416_err("PCA64xx_i2c_read fail\n");
                return ret;
            }
            board_id = PCA6416_get_board_id(client);
            mutex_unlock(&(i2c_dev->mutex));
            read_buf[0] = PCA6416_get_id(board_id, read_buf[0], IIC_PCA_IOCTL_GET_BOM_ID);
            break;

        case IIC_PCA_IOCTL_GET_TYPE_ID:
            mutex_lock(&(i2c_dev->mutex));
            ret = PCA6416_i2c_read(client, PCB_INFO_CMD, read_buf);
            if (ret != 0) {
                mutex_unlock(&(i2c_dev->mutex));
                pca6416_err("PCA64xx_i2c_read fail\n");
                return ret;
            }
            mutex_unlock(&(i2c_dev->mutex));
            read_buf[0] = (char)(((unsigned char)read_buf[0] >> PCA6416_TYPE_ID_BITS) & BOARD_ID_TYPE_MASK);
            break;

        default:
            pca6416_err("PCA64xx cmd error!\n");
            return -EFAULT;
    }

    if (copy_to_user((char *)arg, (void *)read_buf, PCA6416_READ_BUF_LEN)) {
        pca6416_err("copy_to_user failed\n");
        return -EFAULT;
    }

    return 0;
}

STATIC struct file_operations PCA6416_fops = {
    .owner = THIS_MODULE,
    .open = PCA6416_open,
    .release = PCA6416_release,
    .unlocked_ioctl = PCA6416_ioctl,
};

STATIC int PCA6416_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct i2c_adapter *adapter = NULL;
    struct PCA6416_dev *i2c_dev = NULL;
    unsigned char __iomem *base_addr = NULL;
    int ret;

    pca6416_info("PCA64xx_probe enter!\n");
    adapter = client->adapter;
    i2c_dev = get_free_i2c_dev(adapter);
    if (i2c_dev == NULL) {
        pca6416_err("PCA64xx_probe: i2c_dev is NULL!\n");
        return -1;
    }

    pca6416_info("PCA64xx adapter [%s] registered as minor %d\n", adapter->name, i2c_dev->minor);

    i2c_dev->adap = adapter;
    i2c_dev->client = client;

    if (!(base_addr = ioremap(IOMUX_REG_ADDR, IOMUX_REG_LEN))) {
        pca6416_err("PCA64xx ioremap fail!\n");
        return -EFAULT;
    } else {
        writel(IOMUX_I2C_VALUE,
            (volatile unsigned char __iomem *)((unsigned long)(uintptr_t)base_addr + IOMUX_REG_10_OFFSET));
        writel(IOMUX_I2C_VALUE,
            (volatile unsigned char __iomem *)((unsigned long)(uintptr_t)base_addr + IOMUX_REG_14_OFFSET));
        iounmap(base_addr);
        base_addr = NULL;
    }

    ret = alloc_chrdev_region(&devno, 0, 1, PCA6416_NAME);
    if (ret < 0) {
        pca6416_err("%s: alloc_chrdev_region fail! ret = %d\n", PCA6416_NAME, ret);
        return ret;
    }

    cls = class_create(THIS_MODULE, PCA6416_NAME);
    if (IS_ERR(cls)) {
        ret = PTR_ERR(cls);
        pca6416_err("class create error %d\n", ret);
        unregister_chrdev_region(devno, 1);
        return -EBUSY;
    }

    node_device = device_create(cls, NULL, devno, NULL, PCA6416_NAME);
    if (IS_ERR(node_device)) {
        class_destroy(cls);
        unregister_chrdev_region(devno, 1);
        pca6416_err("device create error!\n");
        return -EBUSY;
    }

    cdev_init(&node_cdev, &PCA6416_fops);
    node_cdev.owner = THIS_MODULE;
    ret = cdev_add(&node_cdev, devno, 1);
    if (ret) {
        pca6416_err("Error %d adding node_cdev!\n", ret);
        device_destroy(cls, devno);
        class_destroy(cls);
        unregister_chrdev_region(devno, 1);
        return ret;
    }

    return 0;
}

STATIC int PCA6416_remove(struct i2c_client *client)
{
    struct PCA6416_dev *i2c_dev = NULL;

    if (client == NULL) {
        pca6416_err("invalid parameter,client == NULL\n");
        return -1;
    }

    i2c_dev = i2c_dev_get_by_adapter(client->adapter);
    if (i2c_dev == NULL) {
        pca6416_err("invalid parameter,i2c_dev == NULL\n");
        return -1;
    }
    return_i2c_dev(i2c_dev);

    device_destroy(cls, devno);
    class_destroy(cls);
    unregister_chrdev_region(devno, 1);
    cdev_del(&node_cdev);
    return 0;
}

static struct i2c_driver PCA6416_driver = {
    .driver = {
        .owner		= THIS_MODULE,
        .name	   = "PCA6416",
    },
    .id_table			 = PCA6416_idtable,
    .probe		= PCA6416_probe,
    .remove		= PCA6416_remove,
};

STATIC int __init PCA6416_init(void)
{
    int ret = -1;

    mutex_init(&i2c_dev_array_lock);
    ret = memset_s(i2c_dev_array, sizeof(i2c_dev_array), 0, sizeof(i2c_dev_array));
    if (ret != 0) {
        pca6416_err("memset_s error!\n");
        return -1;
    }

    ret = i2c_add_driver(&PCA6416_driver);
    if (ret != 0) {
        pca6416_err("i2c add PCA64xx_driver error!\n");
        return ret;
    }
    pca6416_info("i2c_add_driver finish!\n");
    return 0;
}

STATIC void __exit PCA6416_exit(void)
{
    i2c_del_driver(&PCA6416_driver);
    (void)mutex_destroy(&i2c_dev_array_lock);
    pca6416_info("PCA64xx_exit finish!\n");
}

MODULE_DESCRIPTION("PCA6416 driver");
MODULE_LICENSE("GPL");

module_init(PCA6416_init);
module_exit(PCA6416_exit);
